/**
 * @file MarkdownParser.ts
 * 
 * 简单的 Markdown 解析器。
 */



//#region 将markdown 直接输出到 html
class HtmlHandler {
    private markdownChange: Markdown = new Markdown;

    /**
     * 能够通过ID获取文本区域和标签，并处理按键松开事件
     * @param id 编辑节点的ID
     * @param output 输出节点的ID
     */
    public TextChangeHandler(id: string, output: string): void {
        let markdown = <HTMLTextAreaElement>document.getElementById(id)
        let markdownOutput = <HTMLLabelElement>document.getElementById(output)

        if (markdown !== null) {
            markdown.onkeyup = (e) => {
                this.RenderHtmlContent(markdown, markdownOutput)
            }

            window.onload = () => {
                this.RenderHtmlContent(markdown, markdownOutput)
            }
        }
    }

    private RenderHtmlContent(markdown: HTMLTextAreaElement, markdownOutput: HTMLLabelElement) {
        if (markdown.value) {
            markdownOutput.innerHTML = this.markdownChange.ToHtml(markdown.value);
        }
        else {
            markdownOutput.innerHTML = "<p></p>";
        }
    }
}
//#endregion

//#region markdown 标签 转换为 HTML 标签
enum TagType {
    Paragraph,
    Header1,
    Header2,
    Header3,
    HorizontalRule,
}

class TagTypeToHtml {
    private readonly tagTypes: Map<TagType, string> = new Map<TagType, string>();

    constructor() {
        this.tagTypes.set(TagType.Paragraph, "p")
        this.tagTypes.set(TagType.Header1, "h1")
        this.tagTypes.set(TagType.Header2, "h2")
        this.tagTypes.set(TagType.Header3, "h3")
        this.tagTypes.set(TagType.HorizontalRule, "hr")
    }

    /**
     * 获取开标签
     * 
     * @example 
     *      
     * OpeningTag(TagType.Header1) // <h1>
     * 
     * @param tagType 标签类型
     */
    public OpeningTag(tagType: TagType): string {
        return this.GetTag(tagType, "<");
    }

    /**
    * 获取闭标签
    *
    * @example
    *
    * ClosingTag(TagType.Header1) // </h1>
    *
    * @param tagType 标签类型
    */
    public ClosingTag(tagType: TagType): string {
        return this.GetTag(tagType, "</");
    }

    /**
    * 获取标签
    *
    * @example
    *
    * GetTag(TagType.Header1,":") // :h1>
    *
    * @param tagType 标签类型
    */
    private GetTag(tagType: TagType, openingTagPattern: string): string {
        let tag = this.tagTypes.get(tagType);
        if (tag !== null) {
            return `${openingTagPattern}${tag}>`;
        }
        return `${openingTagPattern}p>`;
    }

}
//#endregion

//#region 使用MarkdownDocument类存储转换后的标记
/**
 * 使用MarkdownDocument类表示转换后的markdown标记
 */

interface IMarkdownDocument {
    Add(...content: string[]): void;
    Get(): string;
}

/**
 * 存储解析过程中创建的文本
 * 
 * 这里不使用全局字符串：
 * 1. 在使用全局字符串时，会导致在转换HTML的同时还要兼顾将HTML用正确方式写入字符串，
 * 这里违背了单一职责原则（把读markdown和写入html混在了一起）
 * 2. 如果异步添加文本会出现问题
 * 
 */
class MarkdownDocument implements IMarkdownDocument {
    private content: string = ""

    Add(...contents: string[]): void {
        contents.forEach(element => {
            this.content += element;
        })
    }
    Get(): string {
        return this.content
    }
}


class ParseElement {
    CurrentLine: string = ""
}
//#endregion

//#region 访问者模式 更新markdown

interface IVisitor {
    Visit(token: ParseElement, markdownDocument: IMarkdownDocument): void;
}

interface IVisitable {
    Accept(visitor: IVisitor, token: ParseElement, markdownDocument: IMarkdownDocument): void;
}

abstract class VistorBase implements IVisitor {
    constructor(private readonly tagType: TagType, private readonly TagTypeToHtml: TagTypeToHtml) { }

    Visit(token: ParseElement, markdownDocument: IMarkdownDocument): void {
        markdownDocument.Add(this.TagTypeToHtml.OpeningTag(this.tagType), token.CurrentLine, this.TagTypeToHtml.ClosingTag(this.tagType))
    }

}

class ParagraphVisitor extends VistorBase {
    constructor() {
        super(TagType.Paragraph, new TagTypeToHtml());
    }
}

class Header1Visitor extends VistorBase {
    constructor() {
        super(TagType.Header1, new TagTypeToHtml());
    }
}
class Header2Visitor extends VistorBase {
    constructor() {
        super(TagType.Header2, new TagTypeToHtml());
    }
}
class Header3Visitor extends VistorBase {
    constructor() {
        super(TagType.Header3, new TagTypeToHtml());
    }
}

class HorizontalRuleVisitor extends VistorBase {
    constructor() {
        super(TagType.HorizontalRule, new TagTypeToHtml());
    }
}

class Visitable implements IVisitable {
    Accept(visitor: IVisitor, token: ParseElement, markdownDocument: IMarkdownDocument): void {
        visitor.Visit(token, markdownDocument)
    }
}



//#endregion


//#region 通过使用责任链模式决定应用那个标签

abstract class Handler<T>{
    protected next: Handler<T> | null = null;

    public SetNext(next: Handler<T>): void {
        this.next = next;
    }

    public HandleRequest(request: T): void {
        if (!this.CanHandle(request)) {
            if (this.next != null) {
                this.next.HandleRequest(request);
            }
            return;
        }
    }

    protected abstract CanHandle(request: T): boolean;
}

/**
 * 处理有标签行
 */
class ParseChainHandler extends Handler<ParseElement> {

    private readonly visitable: IVisitable = new Visitable();
    /**
     * @param document markdown 实体
     * @param tag 表示markdown 标记 比如 “# ” “## ” “### ” “---”
     * @param visitor 对应的标记的Visitor
     */
    constructor(private readonly document: IMarkdownDocument, private readonly tag: string, private readonly visitor: IVisitor) {
        super();
    }

    protected CanHandle(request: ParseElement): boolean {
        let split = new LineParser().Parse(request.CurrentLine, this.tag);
        if (split[0]) {
            request.CurrentLine = split[1];
            this.visitable.Accept(this.visitor, request, this.document)
        }
        return split[0]
    }
}

/**
 * 特殊处理 没有标签的情况
 */
class ParagraphHandler extends Handler<ParseElement>{
    private readonly visitable: IVisitable = new Visitable();
    private readonly visitor: IVisitor = new ParagraphVisitor();

    protected CanHandle(request: ParseElement): boolean {
        this.visitable.Accept(this.visitor, request, this.document)
        return true;
    }

    constructor(private readonly document: IMarkdownDocument) {
        super();
    }
}

class Header1ChainHandler extends ParseChainHandler {
    constructor(document: IMarkdownDocument) {
        super(document, "# ", new Header1Visitor());
    }
}
class Header2ChainHandler extends ParseChainHandler {
    constructor(document: IMarkdownDocument) {
        super(document, "## ", new Header2Visitor());
    }
}
class Header3ChainHandler extends ParseChainHandler {
    constructor(document: IMarkdownDocument) {
        super(document, "### ", new Header3Visitor());
    }
}
class HorizontalRuleChainHandler extends ParseChainHandler {
    constructor(document: IMarkdownDocument) {
        super(document, "---", new HorizontalRuleVisitor());
    }
}

class ChainOfResponsibilityFactory {
    Build(document: IMarkdownDocument): ParseChainHandler {
        let header1: Header1ChainHandler = new Header1ChainHandler(document);
        let header2: Header2ChainHandler = new Header2ChainHandler(document);
        let header3: Header3ChainHandler = new Header3ChainHandler(document);
        let horizontalRule: Header1ChainHandler = new HorizontalRuleChainHandler(document);
        let paragraph: ParagraphHandler = new ParagraphHandler(document);

        header1.SetNext(header2);
        header2.SetNext(header3);
        header3.SetNext(horizontalRule)
        horizontalRule.SetNext(paragraph)

        return header1
    }
}

/**
 * 帮我们判断一行的前面是否存在markdown标记
 */
class LineParser {
    /**
     * 判断是否存在标签，存在标签就把标签删除
     * 
     * @example
     * let line = new LineParser();
     * line.Parse("## 你好","## ") // [true,"你好"]
     * line.Parse("## 你好","--- ") // [false,"## 你好"]
     * 
     * @param value 判断行的文字
     * @param tag 要判断的标签
     */
    public Parse(value: string, tag: string): [boolean, string] {
        let output: [boolean, string] = [false, ""]
        output[1] = value;
        if (value === "") {
            return output;
        }
        let split = value.startsWith(`${tag}`);
        if (split) {
            output[0] = true;
            output[1] = value.substr(tag.length);
        }
        return output
    }
}

//#endregion

//#region 综合应用
class Markdown {
    /**
     * 
     * @param text markdown标记文本
     * @returns 转换后的HTML
     */
    public ToHtml(text: string): string {
        let document: IMarkdownDocument = new MarkdownDocument();
        let parseChain: ParseChainHandler = new ChainOfResponsibilityFactory().Build(document)
        let lines: string[] = text.split("\n");
        for (let i = 0; i < lines.length; i++) {
            parseChain.HandleRequest(<ParseElement>{ CurrentLine: lines[i] })
        }
        return document.Get();
    }
}
//#endregion

//#region 代码测试

if (true) {
    let markdownDocument = new MarkdownDocument()
    let chain = new ChainOfResponsibilityFactory().Build(markdownDocument)
    chain.HandleRequest(<ParseElement>{ CurrentLine: "## 这是标题2" })
    chain.HandleRequest(<ParseElement>{ CurrentLine: "# 这是标题1" })
    chain.HandleRequest(<ParseElement>{ CurrentLine: "### 这是标题3" })
    chain.HandleRequest(<ParseElement>{ CurrentLine: "--- 这是水平分隔符" })
    chain.HandleRequest(<ParseElement>{ CurrentLine: "这是段落" })
    // let visitable = new Visitable()
    // visitable.Accept(new ParagraphVisitor(), <ParseElement>{ CurrentLine: "这是段落" }, markdownDocument)
    // visitable.Accept(new Header1Visitor(), <ParseElement>{ CurrentLine: "这是标题1" }, markdownDocument)
    // visitable.Accept(new HorizontalRuleVisitor(), <ParseElement>{ CurrentLine: "" }, markdownDocument)

    console.log(markdownDocument.Get())
}

//#endregion